package com.dorukdestan.p2pshare.network;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;

import com.dorukdestan.p2pshare.logger.Logger;
import com.dorukdestan.p2pshare.model.Constants;
import com.dorukdestan.p2pshare.model.Protocol;
import com.dorukdestan.p2pshare.model.SharedFile;
import com.dorukdestan.p2pshare.storage.PeerList;
import com.dorukdestan.p2pshare.util.FileOperations;

public class QueryClient implements Runnable{

	private static Logger logger;
	
	static {
		logger = new Logger(QueryClient.class);
	}
	
	private String queryString;
	private InetAddress inetAddress;
	
	public QueryClient(String queryString, InetAddress inetAddress) {
		this.queryString = queryString;
		this.inetAddress = inetAddress;
	}

	@Override
	public void run() {

		if(queryString == null || inetAddress == null){
			return;
		}
		
		//Initialize socket
		DatagramSocket datagramSocket = null;
		try {
			datagramSocket = new DatagramSocket();
			datagramSocket.setReuseAddress(true);
		} catch (SocketException e) {
			logger.error(e);
		}
		
		//Initialize out packet
		DatagramPacket datagramPacket = 
			new DatagramPacket(queryString.getBytes(), queryString.length(), inetAddress , Constants.UDP_PORT_NUMBER);
		
		//send packet
		try {
			logger.info("Sending: " + queryString);
			datagramSocket.send(datagramPacket);
		} catch (IOException e) {
			logger.error(e);
		} finally{
			if(datagramPacket != null){
				try {
					datagramSocket.setReuseAddress(true);
				} catch (SocketException e) {
					logger.error(e);
				}
				try {
					datagramSocket.setReuseAddress(true);
				} catch (SocketException e) {
					logger.error(e);
				}
				datagramSocket.close();
			}
		}
	}
	
	/**
	 * Sends HELLO message as broadcast
	 * @return clientThread or null
	 */
	public static Thread sendHello(){
		try {
			QueryClient qc = new QueryClient(Protocol.PING, InetAddress.getByName("255.255.255.255"));
			Thread clientThread = new Thread(qc);
			clientThread.start();
			return clientThread;
		} catch (UnknownHostException e) {
			logger.error(e);
		}
		return null;
	}
	
	/**
	 * Sends <code>HELLO_BACK </code>message in a new thread
	 * @param inetAddress
	 */
	public static void sendHelloBack(InetAddress inetAddress){

		String queryString = Protocol.PONG;
		
		QueryClient qc = new QueryClient(queryString, inetAddress);
		new Thread(qc).start();

	}
	
	/**
	 * Searches the file according to the specified mode
	 * and send a <code>FILE_FOUND</code> message if file is found
	 * or <code>NONE</code> message if file is not found
	 * 
	 * @param mode
	 * @param fileNameOrHash
	 * @param inetAddress
	 */
	public static void searchLocalAndSendResponse(String mode, String fileNameOrHash, InetAddress inetAddress){
		SharedFile sharedFile = null;
		String responseString = null;
		
		//Search according to the mode
		if(mode.equalsIgnoreCase(Protocol.SEARCH_MODE_NAME)){
			sharedFile = FileOperations.searchByName(fileNameOrHash);
		}
		else if(mode.equalsIgnoreCase(Protocol.SEARCH_MODE_HASH)){
			try {
				long hash = Long.parseLong(fileNameOrHash);
				sharedFile = FileOperations.searchByHash(hash);
			} catch (Exception e) {
				logger.error(e);
			}
		}
		
		//Check result and prepare response
		if(sharedFile == null){
			responseString = Protocol.NONE;
		}
		else{
			responseString = Protocol.FILE_FOUND + 
							Protocol.DELIMINATOR_CHAR + 
							sharedFile.getHashValue() +
							Protocol.DELIMINATOR_CHAR +
							sharedFile.length() +
							Protocol.DELIMINATOR_CHAR +
							sharedFile.getName();
		}
		
		//Create a new thread to send response
		QueryClient qc = new QueryClient(responseString, inetAddress);
		new Thread(qc).start();
	}
	
	public static void searchRemote(String mode, String fileNameOrHash){
		RemoteSearch remoteSearch = new RemoteSearch(mode, fileNameOrHash);
		new Thread(remoteSearch).start();
	}
	
	private static class RemoteSearch implements Runnable{
		private String mode;
		private String fileNameOrHash;
		private Logger logger;
		
		public RemoteSearch(String mode, String fileNameOrHash) {
			this.mode = mode;
			this.fileNameOrHash = fileNameOrHash;
			logger = new Logger(getClass());
		}

		@Override
		public void run() {
			PeerList.updateNearestPeers();
			PeerList.waitUntillReady();
			
			String queryString = null;
			//Create query string according to the mode
			if(mode.equalsIgnoreCase(Protocol.SEARCH_MODE_NAME)){
				queryString = 
							Protocol.SEARCH + 
							Protocol.DELIMINATOR_CHAR + 
							Protocol.SEARCH_MODE_NAME + 
							Protocol.DELIMINATOR_CHAR + 
							fileNameOrHash;
			}
			else if(mode.equalsIgnoreCase(Protocol.SEARCH_MODE_HASH)){
				queryString = 
					Protocol.SEARCH + 
					Protocol.DELIMINATOR_CHAR + 
					Protocol.SEARCH_MODE_HASH + 
					Protocol.DELIMINATOR_CHAR + 
					fileNameOrHash;
			}
			else{
				logger.error("Invalid search mode given!");
				return;
			}
			
			//Query each nearest peer
			for (InetAddress address : PeerList.getNearestPeers()) {

				//Create a new thread to send query
				QueryClient qc = new QueryClient(queryString, address);
				new Thread(qc).start();
			}
		}
	}
	
}
